package com.tangsm.spring.boot.api.exception;

import com.tangsm.spring.boot.api.base.Result;
import com.tangsm.spring.boot.api.enums.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

/**
 * 全局异常异常处理
 *
 * @author tangsm
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * BaseException异常捕获处理
     *
     * @param e 自定义BusinessException异常类型
     * @return 响应结果
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.OK)
    public Result<?> businessExceptionHandler(BusinessException e) {
        log.warn("业务异常 ==> HttpCode：{}, Message：{}", HttpStatus.OK.value(), e.getMessage());
        return Result.fail(e.getResultCode(), null, e.getMsgParams());
    }

    /**
     * ApiException异常捕获处理
     *
     * @param e 自定义ApiException异常类型
     * @return 响应结果
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.OK)
    public Result<?> apiExceptionHandler(ApiException e) {
        log.warn("API异常 ==> HttpCode：{}, Message：{}", HttpStatus.OK.value(), e.getMessage());
        return Result.fail(e.getResultCode(), null, e.getMsgParams());
    }

    /**
     * BindException异常处理
     * <p>BindException: 作用于@Validated @Valid 注解，仅对于表单提交有效，对于以json格式提交将会失效</p>
     *
     * @param e BindException异常信息
     * @return 响应数据
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> bindExceptionHandler(BindException e) {
        String msg = e.getBindingResult().getFieldErrors()
                .stream()
                .map(n -> String.format("%s: %s", n.getField(), n.getDefaultMessage()))
                .reduce((x, y) -> String.format("%s; %s", x, y))
                .orElse("参数输入有误");

        log.error("BindException 参数校验异常 ==> HttpCode：{}, Message：{}", HttpStatus.BAD_REQUEST.value(), msg, e);
        return Result.fail(ResultCode.PARAM_ERROR, null, msg);
    }

    /**
     * MethodArgumentNotValidException-Spring封装的参数验证异常处理
     * <p>MethodArgumentNotValidException：作用于 @Validated @Valid 注解，接收参数加上@RequestBody注解（json格式）才会有这种异常。</p>
     *
     * @param e MethodArgumentNotValidException异常信息
     * @return 响应数据
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors()
                .stream()
                .map(n -> String.format("%s: %s", n.getField(), n.getDefaultMessage()))
                .reduce((x, y) -> String.format("%s; %s", x, y))
                .orElse("参数输入有误");

        log.error("MethodArgumentNotValidException 参数校验异常 ==> HttpCode：{}, Message：{}", HttpStatus.BAD_REQUEST.value(), msg, e);
        return Result.fail(ResultCode.PARAM_ERROR, null, msg);
    }

    /**
     * ConstraintViolationException-jsr规范中的验证异常，嵌套检验问题
     * <p>ConstraintViolationException：作用于 @NotBlank @NotNull @NotEmpty 注解，校验单个String、Integer、Collection等参数异常处理。</p>
     * <p>注：Controller类上必须添加@Validated注解，否则接口单个参数校验无效</p>
     *
     * @param e ConstraintViolationException异常信息
     * @return 响应数据
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> constraintViolationExceptionHandler(ConstraintViolationException e) {
        String msg = e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining("; "));

        log.error("ConstraintViolationException 参数校验异常 ==> HttpCode：{}, Message：{}", HttpStatus.BAD_REQUEST.value(), msg, e);
        return Result.fail(ResultCode.PARAM_ERROR, null, msg);
    }

    /**
     * 通用异常捕获
     *
     * @param e 异常信息
     * @return 响应结果
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> exceptionHandler(Exception e) {
        log.error("程序异常 ==> HttpCode：{}, Message：{}", HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), e);
        return Result.fail();
    }
}
